#include "../include/soundplayer.h"
#include <stdlib.h>
#include <unistd.h>
#include "FLAC/stream_decoder.h"

#if HAVE_CONFIG_H
#  include <config.h>
#endif

environment_t env;

struct _cd {
	FLAC__uint64 total_samples;
	unsigned sample_rate;
	unsigned channels;
	unsigned bps;
	FLAC__uint32* pcmbuf;
	FILE* in;
};

typedef struct _cd* cd;

FLAC__StreamDecoderReadStatus read_callback(const FLAC__StreamDecoder* decoder, FLAC__byte buffer[], size_t* bytes, void* client_data) {
	//read callback for gathering data from the file pointer
	(void) decoder;
	cd d = (cd) client_data;

	*bytes = fread(buffer, 1, *bytes, d->in); //fill the buffer

	if(0 != *bytes)
		return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
	else
		return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}

FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder, const FLAC__Frame* frame, const FLAC__int32* const buffer[], void* client_data) {
	//hand the decoded buffer to the output module
	(void) decoder;

	//handle all statuses in 3 lines
	if (env->status > SP_PLAY) {
		while (env->status == SP_PAUSE) sleep(1);
		if (env->status > SP_PAUSE) return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
	}

	cd d = (cd) client_data; //cast it here

	d->pcmbuf = (FLAC__uint32*) realloc(d->pcmbuf, frame->header.blocksize*sizeof(FLAC__uint32));  //make a buffer of equal size to buffer
	unsigned int i = 0;

	if (d->total_samples == 0 || d->channels != 2 || d->bps != 16){
		env->message("Please use only 16bit stereo inputs for now\n");
		return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
	}

	//write decoded PCM samples
	while (i <= frame->header.blocksize){ d->pcmbuf[i] = ((FLAC__uint16) buffer[0][i]<<16)|((FLAC__uint16) buffer[1][i+1]); ++i; }
	pcm_output_write(d->pcmbuf, frame->header.blocksize);

	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data) {
	//grabs metadata
	(void) decoder;
	cd d = (cd) client_data; //cast

	/* print some stats */
	if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO){ //record all sorts of useful info
		d->total_samples = metadata->data.stream_info.total_samples;
		d->sample_rate = metadata->data.stream_info.sample_rate;
		d->channels = metadata->data.stream_info.channels;
		d->bps = metadata->data.stream_info.bits_per_sample;

		env->message("sample rate    : %u Hz\nchannels       : %u\nbits per sample: %u\n", d->sample_rate, d->channels, d->bps);
	}
}

void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data) {
	//this appears to be a dummy error handler which just complains
	(void) decoder, (void) client_data;

	env->status=SP_ERROR; //now it actually notifies soundplayer... which will do nothing except complain

	env->message("Got error callback: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
}

void play_flac(FILE* input)
{

	if (env->status != SP_IDLE) goto END;
	env->status = SP_PLAY;

	struct _cd data = {
		.total_samples = 0,
		.sample_rate = 0,
		.channels = 0,
		.bps = 0,
		.pcmbuf = NULL,
		.in = input };
	cd d = &data;

	FLAC__bool ok = true;
	FLAC__StreamDecoder* decoder = 0;
	FLAC__StreamDecoderInitStatus init_status = FLAC__STREAM_DECODER_INIT_STATUS_OK; //in lieu of a uninitialized value

	if ((decoder = FLAC__stream_decoder_new()) == NULL) env->message("ERROR: allocating decoder\n");

	FLAC__stream_decoder_set_md5_checking(decoder, true);
	FLAC__stream_decoder_init_stream(decoder, read_callback, NULL, NULL, NULL, NULL, write_callback, metadata_callback, error_callback, d);
	if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK){
		fprintf(stderr, "ERROR: initializing decoder: %s\n", FLAC__StreamDecoderInitStatusString[init_status]);
		ok = false;
	}

	if (ok){
		ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
		env->message("decoding: %s\n   state: %s\n", ok ? "succeeded" : "FAILED", FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)]);
	}

	FLAC__stream_decoder_delete(decoder);
	free(d->pcmbuf);
	env->status = SP_IDLE;

	END:
		; //stops gcc complaining about label at end of block
}
